#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <openssl/ssl.h>
#include <stdio.h>
#include <openssl/err.h>
#include <dirent.h>

#define SERV_PORT 443
#define SERV_KEY "/home/ubuntu/key"
#define SERV_PEM "/home/ubuntu/pem"
#define SERV_FILE "/home/ubuntu/UNP-source-code.rar"
#define BASE_DIR "/home/ubuntu/dist"
#define UPLOAD_DIR "/home/ubuntu/dist/upload"
#define DEFAULT_HOME "/index.html"
#define DEFAULT_MIME_TYPE "application/octet-stream"
#define MAXLINE 4096
#define LISTENQ 1024
#define MAXHEADER 64

const char *mime_types[] = {
        "text/html", "text/css", "image/svg+xml", "application/javascript", "image/x-icon"
};

const char *suffices[] = {
        "html", "css", "svg", "js", "ico"
};

typedef void SigFunc(int);

struct CharPosition {
    struct ssl_st *s;
    size_t bufferedLength;
    size_t offset;
    char buffer[MAXLINE];
};

struct HeaderNode {
    char *key;
    char *value;
};

struct Request {
    struct HeaderNode *headers[MAXHEADER];
    int header_num;
    char *method;
    char *url;
    char *version;
    char *content;
};

void sig_child(int sigNumber) {
    int stat;
    while (waitpid(-1, &stat, WNOHANG) > 0);
}

void err_sys(const char *fmt, ...) {
    exit(1);
}

int Socket(int domain, int type, int protocol) {
    int n;
    if ((n = socket(domain, type, protocol)) < 0)
        err_sys("socket error");
    return n;
}

void Bind(int fd, const struct sockaddr *addr, socklen_t len) {
    if (bind(fd, addr, len) < 0)
        err_sys("bind error");
}

void Listen(int fd, int n) {
    /* const char *ptr;
    if ((ptr = getenv("LISTENQ")) != NULL)
        backlog = atoi(ptr); */

    if (listen(fd, n) < 0)
        err_sys("listen error");
}

SigFunc *signal(int sig, SigFunc *func) {
    struct sigaction act, outAct;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (sig == SIGALRM)
        act.sa_flags |= SA_INTERRUPT;
    else
        act.sa_flags |= SA_RESTART;
    if (sigaction(sig, &act, &outAct) < 0)
        return SIG_ERR;
    return outAct.sa_handler;
}

SigFunc *Signal(int sig, SigFunc *func) {
    SigFunc *sigFunc;
    if ((sigFunc = signal(sig, func)) == SIG_ERR)
        err_sys("signal error");
    return sigFunc;
}

pid_t Fork(void) {
    pid_t pid;
    if ((pid = fork()) == -1)
        err_sys("fork error");
    return pid;
}

void Close(int fd) {
    if (close(fd) == -1)
        err_sys("close error");
}

ssize_t writen(int fd, const void *buf, size_t n) {
    size_t remaining;
    ssize_t writtenLength;
    const char *ptr;

    ptr = buf;
    remaining = n;
    while (remaining > 0) {
        if ((writtenLength = write(fd, buf, remaining)) <= 0) {
            if (writtenLength < 0 && errno == EINTR)
                writtenLength = 0;
            else
                return -1;
        }
        remaining -= writtenLength;
        ptr += writtenLength;
    }
    return (ssize_t)n;
}

int SSL_writen(struct ssl_st *s, const void *buf, int n) {
    int remaining;
    int writtenLength;
    const char *ptr;

    ptr = buf;
    remaining = n;
    while (remaining > 0) {
        if ((writtenLength = SSL_write(s, buf, remaining)) <= 0) {
            if (writtenLength < 0 && errno == EINTR)
                writtenLength = 0;
            else
                return -1;
        }
        remaining -= writtenLength;
        ptr += writtenLength;
    }
    return n;
}

void Writen(int fd, const void *ptr, size_t n) {
    if (writen(fd, ptr, n) != n)
        err_sys("writen error");
}

void SSL_Writen(struct ssl_st *s, const void *ptr, int n) {
    if (SSL_writen(s, ptr, n) != n)
        err_sys("writen error");
}

int Open(const char *file, int oflag) {
    int fd;
    if ((fd = open(file, oflag)) == -1)
        err_sys("open error");
    return fd;
}

DIR *Opendir(const char *name) {
    DIR *dir;
    dir = opendir(name);
    if (dir == NULL) {
        err_sys("opendir error");
    }
    return dir;
}

void Closedir(DIR *dir) {
    int result = closedir(dir);
    if (result != 0)
        err_sys("closedir error");
}

ssize_t Read(int fd, void *buf, size_t n) {
    ssize_t s;
    if ((s = read(fd, buf, n)) == -1)
        err_sys("read error");
    return s;
}

int SSL_Read(struct ssl_st *ssl, void *buf, int num) {
    int s;
    if ((s = SSL_read(ssl, buf, num)) <= 0)
        err_sys("SSL_read error");
    return s;
}

void http_echo_s(struct ssl_st *s) {
    const char text[] =
            "HTTP/1.0 200 OK\r\n"
            "Content-Type: application/x-rar-compressed\r\n"
            "Content-Length: %ld\r\n"
            "Content-Disposition: attachment;filename=UNP-source-code.rar\r\n"
            "\r\n";

    char buffer[MAXLINE];
    int fd;
    ssize_t readNumber;

    fd = Open(SERV_FILE, O_RDONLY);
    snprintf(buffer, sizeof(buffer), text, lseek(fd, 0, SEEK_END));
    lseek(fd, 0, SEEK_SET);

    SSL_Writen(s, buffer, (int)strnlen(buffer, sizeof(buffer)));
    while ((readNumber = Read(fd, buffer, sizeof(buffer)))) {
        SSL_Writen(s, buffer, (int)readNumber);
    }
}

const struct ssl_method_st *TLS_server_Method() {
    const struct ssl_method_st *ptr;
    if ((ptr = TLS_server_method()) == NULL)
        err_sys("TLS_server_method error");
    return ptr;
}

struct ssl_ctx_st *SSL_CTX_New(const struct ssl_method_st *meth) {
    struct ssl_ctx_st *ptr;
    if ((ptr = SSL_CTX_new(meth)) == NULL)
        err_sys("SSL_CTX_new error");
    return ptr;
}

struct ssl_st *SSL_New(struct ssl_ctx_st *ctx) {
    struct ssl_st *ptr;
    if ((ptr = SSL_new(ctx)) == NULL)
        err_sys("SSL_new error");
    return ptr;
}

void SSL_CTX_Use_certificate_file(struct ssl_ctx_st *ctx, const char *file, int type) {
    if (SSL_CTX_use_certificate_file(ctx, file, type) != 1)
        err_sys("SSL_CTX_use_certificate_file error");
}

void SSL_CTX_Use_PrivateKey_file(struct ssl_ctx_st *ctx, const char *file, int type) {
    if (SSL_CTX_use_PrivateKey_file(ctx, file, type) != 1)
        err_sys("SSL_CTX_use_PrivateKey_file error");
}

void SSL_Set_fd(struct ssl_st *s, int fd) {
    if (SSL_set_fd(s, fd) != 1)
        err_sys("SSL_set_fd error");
}

char GetChar(struct CharPosition *position) {
    int readNumber;
    if (position->bufferedLength == position->offset) {
        readNumber = SSL_Read(position->s, position->buffer, sizeof(position->buffer));
        position->bufferedLength = readNumber;
        position->offset = 0;
    }
    return position->buffer[position->offset++];
}

void GetWord(struct CharPosition *position, char *outWord, int maxSize) {
    int offset = 0, numlines = 0;
    char c;
    bool starting = true;

    while (offset < maxSize - 1) {
        c = GetChar(position);
        if (c == ' ' || c == '\0' || c == '\r' || c == '\n' || c == '\t') {
            if (!starting) break;
            if (c == '\n') numlines++;
            if (numlines == 2) break;
        }
        else {
            starting = false;
            outWord[offset++] = c;
        }
    }
    outWord[offset] = '\0';
    position->offset--;
}



void GetLine(struct CharPosition *position, char *outWord, int maxSize) {
    int offset = 0, numlines = 0;
    char c;
    bool starting = true;

    while (offset < maxSize - 1) {
        c = GetChar(position);
        if (c == '\0' || c == '\r' || c == '\n') {
            if (!starting) break;
            if (c == '\n') numlines++;
            if (numlines == 2) break;
        }
        else {
            starting = false;
            outWord[offset++] = c;
        }
    }
    outWord[offset] = '\0';
    position->offset--;
}

char *GetnChar(struct CharPosition *position, size_t size) {
    char c;
    int offset = 0;
    char *outWord = (char*)malloc(size + 1);
    while (offset < size) {
        c = GetChar(position);
        outWord[offset++] = c;
    }
    outWord[offset] = '\0';
    return outWord;
}

const char *GetSuffix(const char *str, size_t maxN) {
    const char *ptr = str;
    const char *suffix = str;
    while ((*ptr) != '\0' && maxN--) {
        if ((*ptr) == '.' || (*ptr) == '/')
            suffix = ptr + 1;
        ptr++;
    }
    return suffix;
}

const char *GetMime(const char *suffix, size_t maxN) {
    int i;
    for (i = 0; i < (sizeof(suffices)/ sizeof(const char *)); i++) {
        if (strncmp(suffix, suffices[i], maxN) == 0)
            return mime_types[i];
    }
    return DEFAULT_MIME_TYPE;
}

char *GetWordPtr(struct CharPosition *position) {
    char word[MAXLINE];
    char *result;
    ssize_t wordlen;
    GetWord(position, word, sizeof(word));
    if (word[0] == '\0') return NULL;
    wordlen = strnlen(word, MAXLINE);
    result = (char*)malloc(wordlen + 1);
    strncpy(result, word, wordlen);
    result[wordlen] = '\0';
    return result;
}

char *GetLinePtr(struct CharPosition *position) {
    char word[MAXLINE];
    char *result;
    ssize_t wordlen;
    GetLine(position, word, sizeof(word));
    if (word[0] == '\0') return NULL;
    wordlen = strnlen(word, MAXLINE);
    result = (char*)malloc(wordlen + 1);
    strncpy(result, word, wordlen);
    result[wordlen] = '\0';
    return result;
}

void insert(struct Request *request, struct HeaderNode *node) {
    request->headers[request->header_num] = node;
    request->header_num++;
}

struct HeaderNode* find(const char* key, struct Request *request) {
    for (int i = 0; i < request->header_num; i++) {
        if (strncmp(key, request->headers[i]->key, MAXLINE) == 0)
            return request->headers[i];
    }
    return NULL;
}

void parseHeader(struct CharPosition *position, struct Request *request) {
    char *key, *value;
    size_t keyLength;
    request->header_num = 0;
    while(true) {
        key = GetWordPtr(position);
        if (key == NULL) break;
        keyLength = strnlen(key, MAXLINE);
        if (key[keyLength - 1] != ':')
            err_sys("header_format error");
        key[keyLength - 1] = '\0';
        GetChar(position);
        value = GetLinePtr(position);
        struct HeaderNode *node = (struct HeaderNode*)malloc(sizeof(struct HeaderNode));
        node->key = key;
        node->value = value;
        insert(request, node);
    }
}

struct Request* parse(struct CharPosition *position) {
    struct Request *request = (struct Request*)malloc(sizeof(struct Request));
    char *value;
    //method
    value = GetWordPtr(position);
    if (value == NULL) return NULL;
    request->method = value;
    //url
    value = GetWordPtr(position);
    if (value == NULL) return NULL;
    request->url = value;
    //version
    value = GetWordPtr(position);
    if (value == NULL) return NULL;
    request->version = value;
    //headers
    parseHeader(position, request);
    //content

    return request;
}

void parseContent(struct CharPosition *position, struct Request *request) {
    struct HeaderNode *found;
    GetChar(position);
    found = find("Content-Length", request);
    if (found == NULL) request->content = NULL;
    else {
        int content_len = atoi(found->value);
        request->content = GetnChar(position, content_len);
    }
}

void saveFile(struct CharPosition *position, struct Request *request) {
    /* struct HeaderNode *found;
    GetChar(position);
    found = find("Content-Length", request);
    if (found == NULL) request->content = NULL;
    else {
        int content_len = atoi(found->value);
        request->content = GetnChar(position, content_len);
    } */
    struct HeaderNode *contentType, *contentLength, *multipartContentType, *multipartContentDisposition;
    const char *boundary, *fileName;
    char name[MAXLINE] = UPLOAD_DIR "/";
    char *tmp;
    size_t length;
    size_t boundaryLength;
    size_t fileLength;
    size_t contentDispositionLength;
    size_t contentTypeLength;
    int fd;
    int readNumber;

    GetChar(position);
    contentType = find("Content-Type", request);
    if (contentType == NULL) err_sys("saveFile error");
    boundary = contentType->value + 30;
    boundaryLength = strlen(boundary);

    contentLength = find("Content-Length", request);
    if (contentLength == NULL) err_sys("saveFile error");
    length = atol(contentLength->value);

    tmp = GetnChar(position, boundaryLength + 4);
    free(tmp);

    struct Request *multipart = (struct Request*)malloc(sizeof(struct Request));
    parseHeader(position, multipart);

    multipartContentDisposition = find("Content-Disposition", multipart);
    if (multipartContentDisposition == NULL) err_sys("saveFile error");
    contentDispositionLength = strlen(multipartContentDisposition->value);

    multipartContentType = find("Content-Type", multipart);
    if (multipartContentType == NULL) err_sys("saveFile error");
    contentTypeLength = strlen(multipartContentType->value);

    fileLength = length
            - 2 - boundaryLength - 2
            - 21 - contentDispositionLength - 2
            - 14 - contentTypeLength - 2
            - 2
            - 2
            - 2 - boundaryLength - 2 - 2;

    fileName = multipartContentDisposition->value + 34;
    strncat(name, fileName, sizeof(name) - strnlen(name, sizeof(name)));
    name[strnlen(name, sizeof(name)) - 1] = '\0';

    fd = open(name, O_WRONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
    if (fd == -1) err_sys("open error");
    GetChar(position);
    while (fileLength > 0) {
        if (position->bufferedLength == position->offset) {
            readNumber = SSL_Read(position->s, position->buffer, sizeof(position->buffer));
            position->bufferedLength = readNumber;
            position->offset = 0;
        }
        if (position->bufferedLength - position->offset > fileLength) {
            Writen(fd, position->buffer + position->offset, fileLength);
            position->offset += fileLength;
            fileLength = 0;
        } else {
            Writen(fd, position->buffer + position->offset, position->bufferedLength - position->offset);
            fileLength -= position->bufferedLength - position->offset;
            position->offset = position->bufferedLength;
        }
    }
    Close(fd);

    tmp = GetnChar(position, boundaryLength + 8);
    free(tmp);
}

void show(struct Request *request) {
    if (request == NULL) return;
    printf("%s %s %s\r\n", request->method, request->url, request->version);
    for (int i = 0; i < request->header_num; i++) {
        printf("[%s]: [%s]\r\n", request->headers[i]->key, request->headers[i]->value);
    }
    printf("\r\n");
}

void decodeHttp(struct ssl_st *s) {
    struct CharPosition position;
    char buffer[MAXLINE];
    char directory[MAXLINE] = BASE_DIR;
    int fd;
    ssize_t readNumber;
    DIR *dir;
    struct dirent *entries;
    const char text[] =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: %s\r\n"
            "Expires: Fri, 31 Dec 2021 15:59:59 GMT\r\n"
            "last-modified: Mon, 25 Oct 2021 09:21:48 GMT\r\n"
            "Content-Length: %ld\r\n"
            "\r\n";
    const char fileText[] =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/plain\r\n"
            "Transfer-Encoding: chunked\r\n"
            "\r\n";
    const char loginText[] =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: application/json\r\n"
            "Content-Length: 58\r\n"
            "\r\n"
            "{\"status\":\"ok\",\"type\":\"account\",\"currentAuthority\":\"user\"}";
    const char uploadText[] =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: application/json\r\n"
            "Content-Length: 17\r\n"
            "\r\n"
            "{\"status\":\"done\"}";
    const char logOutText[] =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: application/json\r\n"
            "Content-Length: 26\r\n"
            "\r\n"
            "{\"data\":{},\"success\":true}";
    const char currentUserText[] = "HTTP/1.1 200 OK\r\n"
                                   "Content-Type: application/json\r\n"
                                   "Content-Length: 581\r\n"
                                   "\r\n"
                                   "{\"name\":\"Serati Ma\",\"avatar\":\"https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png\",\"userid\":\"00000001\",\"email\":\"antdesign@alipay.com\",\"signature\":\"\",\"title\":\"\",\"group\":\"UED\",\"tags\":[{\"key\":\"0\",\"label\":\"\"},{\"key\":\"1\",\"label\":\"\"},{\"key\":\"2\",\"label\":\"~\"},{\"key\":\"3\",\"label\":\"\"},{\"key\":\"4\",\"label\":\"\"},{\"key\":\"5\",\"label\":\"\"}],\"notifyCount\":12,\"unreadCount\":11,\"country\":\"China\",\"access\":\"user\",\"geographic\":{\"province\":{\"label\":\"Zhejiang\",\"key\":\"330000\"},\"city\":{\"label\":\"Hangzhou\",\"key\":\"330100\"}},\"address\":\"77 Xihuqu Gongzhuanlu\",\"phone\":\"0752-268888888\"}";
    const char errorText[] =
            "HTTP/1.1 501 Not Implemented\r\n"
            "Content-Length: 0\r\n"
            "\r\n";
    const char *mime;
    struct Request *request;

    position.offset = 0;
    position.bufferedLength = 0;
    position.s = s;

    while ((request = parse(&position)) != NULL) {
        strncpy(directory, BASE_DIR, sizeof(directory));
        if (strncmp(request->method, "GET", MAXLINE - 1) == 0) {
            if (strncmp(request->url, "/api/currentUser", MAXLINE - 1) == 0) {
                SSL_Writen(s, currentUserText, (int) strnlen(currentUserText, sizeof(currentUserText)));
            } else if (strncmp(request->url, "/api/rule", sizeof("/api/rule") - 1) == 0) {
                dir = Opendir(UPLOAD_DIR);
                SSL_Writen(s, fileText, (int) strnlen(fileText, sizeof(fileText)));
                while ((entries = readdir(dir)) != NULL) {
                    sprintf(buffer, "%lX\r\n", strnlen(entries->d_name, sizeof(entries->d_name)) + 1);
                    SSL_Writen(s, buffer, (int) strnlen(buffer, sizeof(buffer)));
                    SSL_Writen(s, entries->d_name, (int) strnlen(entries->d_name, sizeof(entries->d_name)));
                    SSL_Writen(s, "/\r\n", (int) strnlen("/\r\n", sizeof("/\r\n")));
                }
                SSL_Writen(s, "0\r\n\r\n", (int) strnlen("0\r\n\r\n", sizeof("0\r\n\r\n")));
                Closedir(dir);
            } else {
                if (strncmp(request->url, "/", MAXLINE - 1) == 0) {
                    mime = "text/html";
                    strncat(directory, "/index.html", MAXLINE - strnlen(BASE_DIR, sizeof(BASE_DIR)));
                } else {
                    mime = GetMime(GetSuffix(request->url, MAXLINE), MAXLINE);
                    strncat(directory, request->url, MAXLINE - strnlen(BASE_DIR, sizeof(BASE_DIR)));
                }
                fd = open(directory, O_RDONLY);
                if (fd == -1) {
                    SSL_Writen(s, errorText, (int) strnlen(errorText, sizeof(errorText)));
                } else {
                    snprintf(buffer, sizeof(buffer), text, mime, lseek(fd, 0, SEEK_END));
                    lseek(fd, 0, SEEK_SET);
                    SSL_Writen(s, buffer, (int) strnlen(buffer, sizeof(buffer)));
                    while ((readNumber = Read(fd, buffer, sizeof(buffer)))) {
                        SSL_Writen(s, buffer, (int)readNumber);
                    }
                }
            }
        } else if (strncmp(request->method, "POST", MAXLINE - 1) == 0) {
            if (strncmp(request->url, "/api/login/account", MAXLINE - 1) == 0) {
                parseContent(&position, request);
                SSL_Writen(s, loginText, (int) strnlen(loginText, sizeof(loginText)));
            }
            else if (strncmp(request->url, "/api/login/outLogin", MAXLINE - 1) == 0) {
                parseContent(&position, request);
                SSL_Writen(s, logOutText, (int) strnlen(logOutText, sizeof(logOutText)));
            }
            else if (strncmp(request->url, "/upload", MAXLINE - 1) == 0) {
                saveFile(&position, request);
                SSL_Writen(s, uploadText, (int) strnlen(uploadText, sizeof(uploadText)));
            }
            else {
                parseContent(&position, request);
                SSL_Writen(s, errorText, (int) strnlen(errorText, sizeof(errorText)));
            }
        } else {
            parseContent(&position, request);
            SSL_Writen(s, errorText, (int) strnlen(errorText, sizeof(errorText)));
        }
        // delete(request);
    }
}

int main(int argc, char **argv) {
    int listenFd, connectionFd;
    pid_t childPid;
    socklen_t cliLen;
    struct sockaddr_in cliAddr, servAddr;
    struct ssl_st *ssl;
    struct ssl_ctx_st *ctx;

    listenFd = Socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servAddr, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(SERV_PORT);
    Bind(listenFd, (struct sockaddr *)&servAddr, sizeof(servAddr));
    Listen(listenFd, LISTENQ);
    Signal(SIGCHLD, sig_child);
    SSL_library_init();
    OpenSSL_add_ssl_algorithms();
    SSL_load_error_strings();
    ctx = SSL_CTX_New(TLS_server_Method());
    SSL_CTX_Use_certificate_file(ctx, SERV_PEM, SSL_FILETYPE_PEM);
    SSL_CTX_Use_PrivateKey_file(ctx, SERV_KEY, SSL_FILETYPE_PEM);

#pragma clang diagnostic push
#pragma ide diagnostic ignored "EndlessLoop"
    while (true) {
        cliLen = sizeof(cliAddr);
        if ((connectionFd = accept(listenFd, (struct sockaddr *)&cliAddr, &cliLen)) < 0) {
            if (errno == EINTR)
                continue;
            else
                err_sys("accept error");
        }
        if ((childPid = Fork()) == 0) {
            Close(listenFd);
            ssl = SSL_New(ctx);
            SSL_Set_fd(ssl, connectionFd);
        SSL_accept_again:
            if (SSL_accept(ssl) != 1) {
                if (errno == EINTR)
                    goto SSL_accept_again;
                else
                    err_sys("SSL_accept error");
            }
            // http_echo_s(ssl);
            decodeHttp(ssl);
            return 0;
        }
        Close(connectionFd);
    }
#pragma clang diagnostic pop
    return 0;
}
